using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using Vk.Methods.Common;
using Vk.Utils;

namespace Vk
{
    public abstract class ApiNode
    {
        public abstract IEnumerable<ApiNode> ChildNodes { get; }
        public abstract bool IsNull { get; }
        public abstract bool? Bool();
        public abstract bool? Bool(string path);
        public abstract DateTime? DateTime(string path);
        public abstract DateTime? DateTime();
        public abstract double? Double(string path);
        public abstract T? Enum<T>(string path) where T : struct;
        public abstract float? Float(string path);
        public abstract int? Int();
        public abstract int? Int(string path);
        public abstract ApiNode SelectNode(string path);
        public abstract IEnumerable<ApiNode> SelectNodes(string path);
        public abstract IEnumerable<T> SelectNodes<T>(string path);
        public abstract short? Short(string path);
        public abstract string String(string path);
        public abstract string String();

        public T Object<T>(string path, Func<ApiNode, T> convertFunc) where T: class
        {
            var node = SelectNode(path);
            if (node != null)
            {
                return convertFunc(node);
            }
            return null;
        }

        public T? Structure<T>(string path, Func<ApiNode, T> convertFunc) where T : struct 
        {
            var node = SelectNode(path);
            if (node != null)
            {
                return convertFunc(node);
            }
            return null;
        }

        public static bool operator ==(ApiNode obj, object anyOther)
        {
            return anyOther == null && obj.IsNull ? true : obj.Equals(anyOther);
        }

        public static bool operator !=(ApiNode obj, object anyOther)
        {
            return anyOther == null && !obj.IsNull ? true : obj.Equals(anyOther);
        }
    }

    public class ApiJsonNode : ApiNode
    {
        private readonly JToken _token;

        public override IEnumerable<ApiNode> ChildNodes
        {
            get { return _token.Children().Select(child => new ApiJsonNode(child)); }
        }

        public override bool IsNull => _token == null;

        public ApiJsonNode(JToken token)
        {
            _token = token;
        }

        public ApiJsonNode(string json)
        {
            _token = JToken.Parse(json);
        }

        public override bool? Bool(string path)
        {
            try
            {
                var element = _token.SelectToken(JPathConverter.FromXPath(path), true);
                return element?.Value<bool>();
            }
            catch
            {
                return null;
            }
        }

        public override bool? Bool()
        {
            try
            {
                return _token?.Value<bool>();
            }
            catch
            {
                return null;
            }
        }

        public override DateTime? DateTime(string path)
        {
            try
            {
                var element = _token.SelectToken(JPathConverter.FromXPath(path), true);
                return new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(element.Value<int>());
            }
            catch
            {
                return null;
            }
        }

        public override DateTime? DateTime()
        {
            try
            {
                return new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(_token.Value<int>());
            }
            catch
            {
                return null;
            }
        }

        public override double? Double(string path)
        {
            var element = _token.SelectToken(JPathConverter.FromXPath(path), false);
            try
            {
                return element?.Value<double>();
            }
            catch
            {
                return null;
            }
        }

        public override T? Enum<T>(string path)
        {
            try
            {
                var element = _token.SelectToken(JPathConverter.FromXPath(path), true);
                var name = element.Value<string>().ToCamelCase();
                return
                    (T)
                    System.Enum.Parse(typeof(T), name);
            }
            catch
            {
                return null;
            }
        }

        public override float? Float(string path)
        {
            var element = _token.SelectToken(JPathConverter.FromXPath(path), false);
            return element?.Value<float>();
        }

        public override int? Int(string path)
        {
            var element = _token.SelectToken(JPathConverter.FromXPath(path), false);
            return element?.Value<int>();
        }

        public override int? Int()
        {
            try
            {
                return _token?.Value<int>();
            }
            catch
            {
                return null;
            }
        }

        public override ApiNode SelectNode(string path)
        {
            return new ApiJsonNode(_token.SelectToken(JPathConverter.FromXPath(path), false));
        }

        public override IEnumerable<ApiNode> SelectNodes(string path)
        {
            return _token.SelectTokens(JPathConverter.FromXPath(path), false).Select(x => new ApiJsonNode(x));
        }

        public override IEnumerable<T> SelectNodes<T>(string path)
        {
            return _token.SelectTokens(JPathConverter.FromXPath(path), false).Select(x => x.Value<T>());
        }

        public override short? Short(string path)
        {
            var element = _token.SelectToken(JPathConverter.FromXPath(path), false);
            return element?.Value<short>();
        }

        public override string String(string path)
        {
            var element = _token.SelectToken(JPathConverter.FromXPath(path), false);
            return element?.Value<string>();
        }

        public override string String()
        {
            return _token.ToString();
        }
    }
}